package netTracker.network;

/**
 *  A class used to create a spanning tree in a network.
 *  Modified by David Cummings from the original source 
 *  by Vijay Garg in "Concurrent and Distributed Systems."
 */

import java.util.LinkedList;

public class SpanTree {
	/** The ID of a node's parent. -1 indicates no parent yet. */
	public int parent = -1;
	/** A list of the addresses of this node's children. */
	public LinkedList<Address> children = new LinkedList<Address>();
	/** Keeps track of the number of reports heard back. */
	int numReports = 0;
	/** Set to true to indicate all children have been heard from. */
	boolean done = false;
	/** The node that is using this spanning tree algorithm. */
	Node myNode;
	/** A lock for the spanning tree. */
	static Object spanningTreeLock = new Object();
	/** The ID of the root of this spanning tree. */
	int leaderID;

	/** 
	 * Creates a spanning tree from a node.
	 * @param initComm used for communication between nodes
	 * @param isRoot indicates if this node is trying to be the root of the spanning tree
	 */
	public SpanTree(Node myNode, boolean isRoot) throws PostalException{
		this.myNode = myNode;
		
		leaderID = -1;	// No leader yet
		
		SpanHandler mySpanHandler = new SpanHandler();
		
		/** Reroute spanning tree messages to our message handler: */
		myNode.getPO().addHandler("invite",mySpanHandler);
		myNode.getPO().addHandler("accept",mySpanHandler);
		myNode.getPO().addHandler("reject",mySpanHandler);
		
		if(isRoot){									// If root of spanning tree:
			parent = myNode.getID();				// Become your own parent
			if (myNode.getPO().enumerateAddresses() <= 1)
				done = true;						// If no other nodes, finish
			else{
				leaderID = myNode.getAddress().getID();	// Set leaderID to self
				Message m = new Message("invite", leaderID, myNode.getAddress());
				myNode.getPO().broadcast(m);		// Broadcast invite to all neighbors
			}
			waitForDone();							// Block until invite is done.
		}
	}

	/**
	 * Blocks until done is set to true, which
	 * indicates all children have been heard from.
	 */
	public void waitForDone() {
		synchronized(spanningTreeLock){
			while (!done){
				try{
					spanningTreeLock.wait();
				}catch(InterruptedException e){
					System.err.println(e);
				}
			}
			System.out.println("Node "+myNode.getID()+" has heard from all children.");
		}
	}
	
	/**
	 * Returns true if this node is the root of the spanning tree or "leader".
	 * @return true if this node is the root of the spanning tree or "leader".
	 */
	public boolean isLeader(){
		return (parent == myNode.getID());
	}

	public String getChildren(){
		String childrenString;
		if(children.size()>0){
			childrenString=("Node "+myNode.getID()+" children: ");
			for(int i=0; i<children.size()-1; i++)	// Add children+comma until last child
				childrenString+=(String.valueOf(children.get(i).getID())+", ");
			childrenString+=(String.valueOf(children.get(children.size()-1).getID()));
		}else{
			childrenString=("Node "+myNode.getID()+" has no children.");
		}
		return childrenString;
	}
	
	public String getParent(){
		return ("Node "+myNode.getID()+" parent: "+parent);
	}
	
	/**
	 * Internal class for handling span tree messages.
	 */
	class SpanHandler implements MsgHandler{
		
		/**
		 * Handles receiving a spanning tree related message.
		 * @param m
		 * @param src
		 * @param tag
		 */
		public void handleMsg(Message m) throws PostalException{
			synchronized(spanningTreeLock){
			
				int num = m.getNumber();				// Get campaign number from the message.
				
				/** Print message information: */
				System.out.println(myNode.getID()+" "+m.getTag()+" -> "+m.getSrc().getID()+"\t[CAMPAIGN "+num+"]");	
				LeaderElectionGUI.numMessages++;
				
				if (m.getTag().equals("invite")) {		// Upon receiving an invite message:	
					if (num > leaderID){				// If old leader is usurped:
						leaderID = num;					// Change leaderID.
						numReports = 0;					// Reset number of reports.
						children = new LinkedList<Address>();	// Reset children.
						elect(m.getSrc());				// Elect message sender.
					}else if(num < leaderID){			// If old leader wins, send election message back.
						myNode.getPO().send(new Message("invite", leaderID, myNode.getAddress()), m.getSrc());
					}else{								// If message is from current election:
						if (parent == -1) {				// If no parent yet:
							elect(m.getSrc());			// Elect message sender. (send accept, broadcast invite)
						} else {						// If we already have a parent:
														// Send back reject message.
							myNode.getPO().send(new Message("reject", leaderID, myNode.getAddress()), m.getSrc()); 	
						} 	// end if no parent yet
					} // end if num > leaderID
				} else if ( (num == leaderID) &&		// Only process accept/reject message if it is for the current campaign
							(m.getTag().equals("accept")) || (m.getTag().equals("reject"))) {
					if (m.getTag().equals("accept"))	// If we receive an accept message,
						children.add(m.getSrc());		// Add the sender to our children.
					numReports++;						// For either accept or reject, increase the number of reports.
					
					// If the number of reports equals the number of neighbors, we have heard back from all neighbors. 
					// We subtract 1 to remove ourselves from the count.
					if (numReports >= (myNode.getPO().enumerateAddresses()-1)) {
						done = true;
						// System.out.println("Thread "+myNode.getID()+" is done.");
						spanningTreeLock.notifyAll();
					}
				}// end if message tag equals accept/reject
			} // end synchronized(spanningTreeLock)
		} // end handleMsg
		
		private void elect(Address newLeader) throws PostalException{
			numReports++;					// increment number of reports to account for parent
			parent = newLeader.getID();		// set parent to new leader
			myNode.getPO().send(new Message("accept", leaderID, myNode.getAddress()), newLeader); 	// Send accept message
			
			/** Broadcast invite message to all neighbors besides ourselves and our parent: */
			int N = myNode.getPO().enumerateAddresses();// Find number of neighbors
			for (int i = 0; i < N; i++){				// As long as ID is not ours or the parents:
				if ((i != myNode.getID()) && (i != newLeader.getID())){
					myNode.getPO().send(new Message("invite", leaderID, myNode.getAddress()), 
							myNode.getPO().IDtoAddress(i));	// Send invite message
				} // end if
			} // end for
			waitForDone();	// Block until election is over.
		}
	} // end SpanHandler
}
